package common;

import org.junit.Test;

import java.lang.reflect.Array;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * String类的学习
 * String 为什么要设计成不可变的呢？
      首先String类是用final关键字修饰，这说明String不可继承。
      String类的主力成员字段value是个char[ ]数组，而且是用final修饰的。final修饰的字段创建以后就不可改变。说明String对象的内容创建后不可改变
      但不代表char[]里面的内容不可变，这里要知道，final修饰的是char[]的地址，内容是可以被设置的，但是为什么String又不能改变呢
      String是不可变的关键都在底层的实现，而不是一个final。详细看 private final char value[];  其是private的，说明不能被外面访问。所以，String的设计，
      考验的是工程师构造数据类型，封装数据的功力。
 * <p>

 * String 类不可变有什么好处：安全。就是为了安全
 *        1.当String支持非可变性的时候，它们的值很好确定，不管调用哪个方法，都互不影响。
 *          如果想让他改变，实际上StringBuffer的作用就是起到了String的可变配套类角色。
 *          再比如：在HashSet和HashMap中，键是可变的，那么就破坏了 HashSet键值的唯一性 。所以千万不要用可变类型做 HashMap和HashSet键值。
 *        2.不可变性支持线程安全：不可变对象不能被写，所以线程安全。
 *        3.不可变性支持字符串常量池，例如：
 *                String one = "someString";
 *                String two = "someString";
 * 虽然String不可变，但是存的值可以放入常量池，这样，one和two就指向常量池，因为不可变，常量池的值也不可能被随意修改，创建就创建了，要是内存里字符串内容能改来改去，这么做就完全没有意义了。
 * 这样在大量使用字符串的情况下，可以节省内存空间，提高效率。
 * <p>
 *             最后说一下，阿里巴巴的java操作手册说了一句话，很棒：
 *             String 重写了 hashCode 和 equals 方法，所以我们可以非常愉快地使用 String 对象作为 key 来使用。
 */

public class Example_String {


    /**
     * 旧值： [abc, def, chd, hh]
     * 新值： [abc, def, chd, 34]
     * 这个例子说明，String对象虽然是final修饰的，但是只是修饰的是String的引用，如果String[]的值改变了，其实也是可以的，
     * 但是绝对不能将str1赋值给其他数组对象，因为str1现在是不可变的
     * <p>
     * 测试是对的 ，说明呀！String[]是可以把引用改变的
     */
    @Test
    public void testString1() {
        String[] str1 = new String[]{"abc", "def", "chd", "hh"};

        //这里注意：Arrays.toString()可以将数组以元素方式打印出来
        System.out.println(Arrays.toString(str1));
        //Arrays.set()将指定的数组的第几个元素修改成新的值，利用的是反射
        Array.set(str1, 3, "34");
        System.out.println(Arrays.toString(str1));

        //尝试将str1赋值给另外一个String[] 数组
        System.out.println(str1);
        str1 = new String[]{"新的String集合"};
        System.out.println(str1);

        //测试将String 对象赋值给另外一个对象
        String string1 = new String("aaasdfffffffffffffffffg");
        string1 = "bbb";
        System.out.println(string1);

        System.out.println(string1);
    }

    /**
     * 1.常量和常量的拼接结果在常量池，原理是编译器优化（打印出来字节码，已经将字符串加起来了）
     * 2.常量池不会存相同的字符串
     * 3.只要有一个是变量，结果就存在堆中，变量拼接的原理是StringBuilder
     * 4.如果拼接的结果调用intern()方法，则主动将常量池中还没有的字符串对象刷新到对象池中 ，并返回此对象地址。
     * 5.使用StringBuilter的append()方法添加字符串效率会远高于使用String的重载符“+”，因为String的“+”号底层是创建了一个StringBuilder对象，使用append()方法。
     *   效率将近差别几千倍
     */
    @Test
    public void test2(){
        String s1="javaEE";
        String s2="hadoop";

        String s3="javaEEhadoop";
        String s4="javaEE"+"hadoop";
        String s5=s1+"hadoop";
        String s6=s1+s2;
        System.out.println(s3==s4); //true
        System.out.println(s3==s5); //false
        System.out.println(s3==s6);//false
        String intern = s6.intern();
        System.out.println(s3==intern);//true
    }


    @Test
    public void test12(){
        String s="hadoop";
        String s1=new String("hadoop");
        String intern = s1.intern();
        System.out.println(s1==s); //false
        System.out.println(s==intern); //true

        String s2=new String("had")+new String("oop");
        String intern1 = s2.intern();
        System.out.println(s==intern1); //true

    }

    /**
     * 怎么将一个集合转化成数组，这个很简单，一下子想到的就是使用list.toArray(),看下面例子：
     * 发现了没有，直接转的话，默认是转成了Object类型，如果Object类型再转成String会爆出ClassCastException
     * <p>
     * 正确做法：利用list.toArray(T[] t )
     */
    @Test
    public void listToArray() {
        List<String> list = new ArrayList<>();
        list.add("aaa");
        list.add("bbb");
        Object[] objects = list.toArray();
        // String[] objectsToString = (String[]) list.toArray();
        System.out.println(Arrays.toString(objects));
        // System.out.println(objectsToString);

        String[] str = new String[list.size()];
        String[] strings = list.toArray(str);
        System.out.println(Arrays.toString(strings));
    }

    /**
     * 使用工具类 Arrays.asList()把数组转换成集合时，不能使用其修改集合相关的方
     * 法，它的 add/remove/clear 方法会抛出 UnsupportedOperationException 异常。
     * 说明：asList 的返回对象是一个 Arrays 内部类，并没有实现集合的修改方法。Arrays.asList
     * 体现的是适配器模式，只是转换接口，后台的数据仍是数组。
     * <p>
     * 第二种情况  第二种情况：str[0] = "gujin"; 那么 list.get(0)也会随之修改。
     */

    @Test
    public void ArrayAsList() {
        String[] str = new String[]{"aaa", "bbb"};
        List<String> strings = Arrays.asList(str);
        //  strings.add("ccc");//java.lang.UnsupportedOperationException
        System.out.println(strings);
        str[0] = "hahha";
        System.out.println(strings);
    }

    /**
     * 不要在 foreach 循环里进行元素的 remove/add 操作。remove 元素请使用 Iterator
     * 方式，如果并发操作，需要对 Iterator 对象加锁。
     */
    @Test
    public void testIterator() {
        List<Integer> a = new ArrayList<>(Arrays.asList(1, 2));

        //正确
//        Iterator<Integer> it = a.iterator();
//        while (it.hasNext()) {
//            Integer temp = it.next();
//            if (1==temp) {
//                it.remove();
//            }
//        }

        //错误 java.util.ConcurrentModificationException
        for (Integer b : a) {
            if (2 == b) {
                a.remove(b);
            }
        }

    }

    /**
     * java集合遍历时候，尽量使用entrySet,而不是keySet，因为keySet本质是遍历了两次，第一次是专为Iterator对象，第二次是从HashMap中取出key所对应的value
     * 如果是jdk1.8  推荐使用Map.foreach
     * <p>
     * values()返回的是 V 值集合，是一个 list 集合对象；keySet()返回的是 K 值集合，是
     * 一个 Set 集合对象；entrySet()返回的是 K-V 值组合集合。
     */
    @Test
    public void testForEach() {
        Map<String, String> maps = new HashMap<>();
        maps.put("aaa", "1");
        maps.put("bbb", "2");
        maps.put("ccc", "3");
        maps.put("ddd", "4");
        // maps.forEach(System.out::println);
    }

    /**
     * ArrayList的subList结果不可强转成ArrayList，否则会抛出ClassCastException
     * 异常：java.lang.ClassCastException: java.util.ArrayList$SubList cannot be cast to java.util.ArrayList;
     * 说明：subList 返回的是 ArrayList 的内部类 SubList，并不是 ArrayList ，而是
     * ArrayList 的一个视图，对于 SubList 子列表的所有操作最终会反映到原列表上。
     *
     * 以下代码：当在oldList当中插入一个元素的时候，原来的subList()发生了错误：
     *
     *  java.util.ConcurrentModificationException
     */
    @Test
    public void testArrayListVSSublist() {
        List<String> strings = Arrays.asList("aaa","bbb","ccc","ddd","eee");
        List<String> oldList = new ArrayList(strings);
        System.out.println(oldList);
        List<String> newList = oldList.subList(1, 3);
        System.out.println(newList);
       // oldList.add(2,"哈哈哈");
      //  System.out.println(newList);
       ArrayList  newArraylist=(ArrayList) newList;
        System.out.println(newArraylist);
    }


    /**
     * 测试ConcurrentHashMap能不能存储  null的键值对
     * 测试结果为：
     *     key为null时：报错：java.lang.NullPointerException  NPE
     *     value为null时：报错：java.lang.NullPointerException  NPE
     *
     * 说明：ConcurrentHashMap不能存null的 key value键值对（Hashtable也不能）
     * 拓展：HashMap 可以存key为null的键，多个null值，当多个key为nul，被覆盖。
     *      TreeMap 不可以存key为null的键，但是可以存value为null的值。
     */
    @Test
    public void testConcurrentHashMapSetNullKeyAndValue(){
        Map<String,String> currentHashMap=new ConcurrentHashMap<>();
        currentHashMap.put("aaa","1");
       // currentHashMap.put(null,"2");
       // currentHashMap.put("bbb",null);
       // System.out.println(currentHashMap);

        //测试HashMap
        Map<String,String> hashMap=new HashMap<>();
        hashMap.put("aaa",null);
        hashMap.put("bbb",null);
        hashMap.put(null,null);//被覆盖
        hashMap.put(null,"1");
        System.out.println(hashMap);
    }

    /**
     * ++i:先增加，后引用,++i 可以 作为左值
     * i++: (先引用，后增加)返回原来的值    i++ 不能作为左值
     *
     */
    @Test
    public void testOperationSymbol(){
        int i=0;
        try{
            int k=  ++i;
            System.out.println(k);
        }finally {
          int k=  ++i;
            System.out.println(k);
        }
        System.out.println(i);

    }

    /**
     *   i++: 先引用当前得值，再增加
     */
    @Test
    public void testOperationSymbol1(){
        int i=0;
        try{
            i++;
        }finally {
            i++;
        }
        System.out.println(i);

    }
}
